/*
 * Copyright (C) 2018-2025 Toshiaki Maki <makingx@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package am.ik.yavi.arguments;

import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;

import am.ik.yavi.core.ConstraintContext;
import am.ik.yavi.core.ConstraintGroup;
import am.ik.yavi.core.ConstraintViolationsException;
import am.ik.yavi.core.Validatable;
import am.ik.yavi.core.Validated;
import am.ik.yavi.core.ValueValidator;
import am.ik.yavi.jsr305.Nullable;

/**
 * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh
 *
 * @since 0.3.0
 */
@FunctionalInterface
public interface Arguments1Validator<A1, X> extends ValueValidator<A1, X> {

	/**
	 * Convert {@link Validatable} instance into {@link Arguments1Validator}
	 * @param validator core validator
	 * @param <X> target class
	 * @return arguments1 validator
	 * @since 0.8.0
	 */
	static <X> Arguments1Validator<X, X> from(Validatable<X> validator) {
		return Arguments1Validator.from(validator.applicative());
	}

	/**
	 * Convert {@link ValueValidator} instance into {@link Arguments1Validator}
	 * @param valueValidator value validator
	 * @param <A1> class of argument1
	 * @param <X> target class
	 * @return arguments1 validator
	 * @since 0.8.0
	 */
	static <A1, X> Arguments1Validator<A1, X> from(ValueValidator<A1, X> valueValidator) {
		return valueValidator::validate;
	}

	/**
	 * Convert an Arguments1Validator that validates Arguments1&lt;A1&gt; to an
	 * Arguments1Validator&lt;A1, X&gt;
	 * @param validator validator for Arguments1&lt;A1&gt;
	 * @param <A1> class of argument1
	 * @param <X> target class
	 * @return arguments1 validator that takes an A1 directly
	 * @since 0.16.0
	 */
	static <A1, X> Arguments1Validator<A1, X> unwrap(Arguments1Validator<Arguments1<A1>, X> validator) {
		return new Arguments1Validator<A1, X>() {
			@Override
			public Validated<X> validate(A1 a1, Locale locale, ConstraintContext constraintContext) {
				return validator.validate(Arguments.of(a1), locale, constraintContext);
			}

			@Override
			public Arguments1Validator<A1, Supplier<X>> lazy() {
				return Arguments1Validator.unwrap(validator.lazy());
			}
		};
	}

	@Override
	Validated<X> validate(@Nullable A1 a1, Locale locale, ConstraintContext constraintContext);

	/**
	 * Convert this validator to one that validates Arguments1 as a single object.
	 * @return a validator that takes an Arguments1
	 * @since 0.16.0
	 */
	default Arguments1Validator<Arguments1<A1>, X> wrap() {
		return new Arguments1Validator<Arguments1<A1>, X>() {
			@Override
			public Validated<X> validate(Arguments1<A1> args, Locale locale, ConstraintContext constraintContext) {
				final Arguments1<? extends A1> nonNullArgs = Objects.requireNonNull(args);
				return Arguments1Validator.this.validate(nonNullArgs.arg1(), locale, constraintContext);
			}

			@Override
			public Arguments1Validator<Arguments1<A1>, Supplier<X>> lazy() {
				return Arguments1Validator.this.lazy().wrap();
			}
		};
	}

	/**
	 * @deprecated Use {@link #map(Function)} instead.
	 * @since 0.7.0
	 */
	@Override
	@Deprecated
	default <X2> Arguments1Validator<A1, X2> andThen(Function<? super X, ? extends X2> mapper) {
		return this.map(mapper);
	}

	/**
	 * Maps the validated value to a new type using the provided mapper function. This is
	 * a transformation operation that applies the function only if validation succeeds.
	 * @param mapper function to transform the validated value
	 * @param <X2> the type after transformation
	 * @return a value validator that applies the mapping function after validation
	 * @since 0.17.0
	 */
	@Override
	default <X2> Arguments1Validator<A1, X2> map(Function<? super X, ? extends X2> mapper) {
		return new Arguments1Validator<A1, X2>() {
			@Override
			public Validated<X2> validate(A1 a1, Locale locale, ConstraintContext constraintContext) {
				return Arguments1Validator.this.validate(a1, locale, constraintContext).map(mapper);
			}

			@Override
			public Arguments1Validator<A1, Supplier<X2>> lazy() {
				return Arguments1Validator.this.lazy()
					.map((Function<Supplier<X>, Supplier<X2>>) xSupplier -> () -> mapper.apply(xSupplier.get()));
			}
		};
	}

	/**
	 * @since 0.11.0
	 */
	@Override
	default <X2> Arguments1Validator<A1, X2> andThen(ValueValidator<? super X, X2> validator) {
		return new Arguments1Validator<A1, X2>() {
			@Override
			public Validated<X2> validate(A1 a1, Locale locale, ConstraintContext constraintContext) {
				return Arguments1Validator.this.validate(a1, locale, constraintContext)
					.flatMap(v -> validator.validate(v, locale, constraintContext));
			}

			@Override
			public Arguments1Validator<A1, Supplier<X2>> lazy() {
				return Arguments1Validator.this.lazy()
					.andThen((xSupplier, locale, constraintContext) -> validator
						.validate(Objects.requireNonNull(xSupplier).get(), locale, constraintContext)
						.map(x2 -> () -> x2));
			}
		};
	}

	/**
	 * @since 0.7.0
	 */
	@Override
	default <A> Arguments1Validator<A, X> compose(Function<? super A, ? extends A1> mapper) {
		return new Arguments1Validator<A, X>() {
			@Override
			public Validated<X> validate(A a, Locale locale, ConstraintContext constraintContext) {
				return Arguments1Validator.this.validate(mapper.apply(a), locale, constraintContext);
			}

			@Override
			public Arguments1Validator<A, Supplier<X>> lazy() {
				return Arguments1Validator.this.lazy().compose(mapper);
			}
		};
	}

	/**
	 * @since 0.10.0
	 */
	default Arguments1Validator<A1, Supplier<X>> lazy() {
		throw new UnsupportedOperationException("lazy is not implemented!");
	}

	default Validated<X> validate(@Nullable A1 a1) {
		return this.validate(a1, Locale.getDefault(), ConstraintGroup.DEFAULT);
	}

	default Validated<X> validate(@Nullable A1 a1, ConstraintContext constraintContext) {
		return this.validate(a1, Locale.getDefault(), constraintContext);
	}

	default Validated<X> validate(@Nullable A1 a1, Locale locale) {
		return this.validate(a1, locale, ConstraintGroup.DEFAULT);
	}

	default X validated(@Nullable A1 a1) throws ConstraintViolationsException {
		return this.validate(a1).orElseThrow(ConstraintViolationsException::new);
	}

	default X validated(@Nullable A1 a1, ConstraintContext constraintContext) throws ConstraintViolationsException {
		return this.validate(a1, constraintContext).orElseThrow(ConstraintViolationsException::new);
	}

	default X validated(@Nullable A1 a1, Locale locale) throws ConstraintViolationsException {
		return this.validate(a1, locale).orElseThrow(ConstraintViolationsException::new);
	}

	default X validated(@Nullable A1 a1, Locale locale, ConstraintContext constraintContext)
			throws ConstraintViolationsException {
		return this.validate(a1, locale, constraintContext).orElseThrow(ConstraintViolationsException::new);
	}

	/**
	 * @since 0.7.0
	 */
	default <A2, Y> Arguments2Splitting<A1, A2, X, Y> split(ValueValidator<A2, Y> validator) {
		return new Arguments2Splitting<>(this, validator);
	}

	/**
	 * @since 0.7.0
	 */
	default <Y> Arguments2Combining<A1, X, Y> combine(ValueValidator<A1, Y> validator) {
		return new Arguments2Combining<>(this, validator);
	}

	/**
	 * @since 0.7.0
	 */
	@Override
	default Arguments1Validator<A1, X> indexed(int index) {
		return (a1, locale, constraintContext) -> Arguments1Validator.this.validate(a1, locale, constraintContext)
			.indexed(index);
	}

	/**
	 * @since 0.8.0
	 */
	default <C extends Collection<X>> Arguments1Validator<Iterable<A1>, C> liftCollection(Supplier<C> factory) {
		return Arguments1Validator.from(ValueValidator.super.liftCollection(factory));
	}

	/**
	 * @since 0.8.0
	 */
	default Arguments1Validator<Iterable<A1>, List<X>> liftList() {
		return Arguments1Validator.from(ValueValidator.super.liftList());
	}

	/**
	 * @since 0.8.0
	 */
	default Arguments1Validator<Iterable<A1>, Set<X>> liftSet() {
		return Arguments1Validator.from(ValueValidator.super.liftSet());
	}

	/**
	 * @since 0.8.0
	 */
	default Arguments1Validator<Optional<A1>, Optional<X>> liftOptional() {
		return Arguments1Validator.from(ValueValidator.super.liftOptional());
	}

}
